Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust coingecko prices based on token decimals #2899

Merged
merged 4 commits into from
Aug 19, 2024

Conversation

MartinquaXD
Copy link
Contributor

Description

Fixes: #2887

Changes

Adjust returned native token prices when the quoted token has a different amount of decimals than the native token to be inline with all other native price estimator implementations.

Additionally I also removed the option to quote without denominating in any token mainly to reduce complexity.

How to test

Added an ignored unit test that checks that prices make sense.
Also compared prices returned for wxdai and usdc with prices logged in our infra.

@MartinquaXD MartinquaXD marked this pull request as ready for review August 15, 2024 15:28
@MartinquaXD MartinquaXD requested a review from a team as a code owner August 15, 2024 15:28

let (prices_in_eth, infos) = tokio::try_join!(
self.bulk_fetch_denominated_in_eth(&tokens),
self.infos.get_token_infos(&tokens_vec).map(Result::Ok),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to change the interface of get_token_infos() to accept a reference to something that implement Iterator? that way we shouldn't need to re-allocate a new vector just to pass it as reference any way 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically a good idea but that doesn't play nice with automock and async_trait unfortunately. :/

crates/shared/src/price_estimation/native/coingecko.rs Outdated Show resolved Hide resolved
tokens
.iter()
.map(|t| {
let decimals = if *t == usdc { Some(6) } else { Some(18) };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible that a token has more than 18 decimals? if yes, could you please add a token like that in that test to check that the reverse operation (powi with a negative number + then multiplication to normalize it) works as expected? 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good idea. This was actually a problem in the original fix for the oneinch estimator adjustment since we used u64 operations there and 10 ** 21 overflowed that value which caused other issues.

) -> Result<HashMap<H160, NativePriceEstimateResult>, PriceEstimationError> {
tokens.insert(denominator);
let prices_in_eth = self.bulk_fetch_denominated_in_eth(&tokens).await?;
tokens.insert(self.denominator.address);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we deliberately removing the optimisation of not requesting the price for weth if not needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this was a conscious decision for the sake of simplicity. I'm happy to put the optimization back in if we actually need the extra 5% of batch size but for now I would prefer to keep the code leaner since this file is already on the more complex side IMO.

@@ -158,19 +178,36 @@ impl CoinGecko {
token: &Token,
denominator_price_eth: Decimal,
prices: &HashMap<Token, f64>,
infos: &HashMap<Token, TokenInfo>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method signature is quite 🤡 in my opinion (it has 5 values of which some are redundant).

I think we should simply pass it the token as (TokenInfo, priceInEth) and the denominator as (Denominator, priceInEth) and keep this method for the pure non-trivial math.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative to this function seems to be even more 🤡 error handling in the calling function. While your suggestion would make the conversion more pure it would make the surrounding code way harder to read.
Unless I'm missing some trick to reasonably manage errors (using anonymous closures to get early returns doesn't count IMO).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the fact that you are passing in denominator_price_eth on top of prices is just redundant (and seems inconsistent) so at least you could either

  • remove denominator_price_eth and and "expect" on it being in prices
  • pass the token price into the function directly

But I also don't really see how error handling is an issue here (you are bubbling up errors in any case)

.checked_div(denominator_price_eth)
.context("division by zero")?
.checked_mul(adjustment)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do the adjustment before or after the division to not lose precision?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Since this is not using a float under the hood but a (base: i64, scale: u32) the order of operations should not matter AFAIU.

Copy link
Contributor

@squadgazzz squadgazzz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@fleupold fleupold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothin blocking, and we should probably try to get this into the release so that coingecko can have some impact.

@@ -158,19 +178,36 @@ impl CoinGecko {
token: &Token,
denominator_price_eth: Decimal,
prices: &HashMap<Token, f64>,
infos: &HashMap<Token, TokenInfo>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the fact that you are passing in denominator_price_eth on top of prices is just redundant (and seems inconsistent) so at least you could either

  • remove denominator_price_eth and and "expect" on it being in prices
  • pass the token price into the function directly

But I also don't really see how error handling is an issue here (you are bubbling up errors in any case)

@MartinquaXD MartinquaXD merged commit 349bd0a into main Aug 19, 2024
9 of 10 checks passed
@MartinquaXD MartinquaXD deleted the coingecko-decimals-adjustment branch August 19, 2024 06:09
@github-actions github-actions bot locked and limited conversation to collaborators Aug 19, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

bug: CoinGecko estimator does not normalize price for tokens which don't have 18 decimals
4 participants